import os
import re
import wx
import sys
import time
import json
import shutil
import psutil
import winreg
import wx._xml
import asyncio
import datetime
import threading
import subprocess
import webbrowser
import wx.richtext




MAIN_PATH = os.path.dirname(os.path.abspath(sys.argv[0]))
JSON_PATH = os.path.join(MAIN_PATH, "xuni.json")
DIST_PATH = os.path.join(MAIN_PATH, "dist")

BUILD_PATH = os.path.join(MAIN_PATH, "pyd_build")
NUITKACACHE_PATH = os.path.join(MAIN_PATH, "nuitka_cache")
PYINSTALLERCACHE_PATH = os.path.join(MAIN_PATH, "pyinstaller_cache")
PIPCACHE_PATH = os.path.join(MAIN_PATH, "pip_cache")


os.makedirs(DIST_PATH, exist_ok=True)
os.makedirs(BUILD_PATH, exist_ok=True)
os.makedirs(NUITKACACHE_PATH, exist_ok=True)
os.makedirs(PYINSTALLERCACHE_PATH, exist_ok=True)
os.makedirs(PIPCACHE_PATH, exist_ok=True)



def get_py_reg():
    try:
        py_list = set()
        key = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, "Local Settings\\Software\\Microsoft\\Windows\\Shell\\MuiCache")
        for v in range(winreg.QueryInfoKey(key)[1]):
            value = winreg.EnumValue(key, v)
            if "python.exe" in value[0]:
                main_py_path = os.path.dirname(value[0])
                if all([os.path.exists(os.path.join(main_py_path, lib)) for lib in ["DLLs", "Doc", "include", "Lib", "libs", "Scripts"]]):
                    py_list.add(main_py_path + "\\python.exe")
        winreg.CloseKey(key)
        return list(py_list)
    except:
        return [""]


def get_py_xuni_version(path):
    try:
        with open(path, "r", encoding="utf-8") as f:
            return re.search("version.*?= (.*?)\n", f.read(), re.S).group(1)
    except Exception as e:
        print("虚拟环境版本查询失败:", e)
        return "0"

def parse_arguments(args):
    commd = { }
    i = 0
    while i < len(args):
        current_arg = args[i]
        if current_arg.startswith('-'):
            # 检查是否有下一个元素作为值
            if i + 1 < len(args):
                next_value = args[i + 1]
                commd[current_arg.replace("-", "")] = next_value
                i += 2  # 跳过下一个元素
            else:
                commd[current_arg.replace("-", "")] = None  # 没有值
                i += 1
        else:
            i += 1
    return commd


def open_file():
    try:
        if os.path.exists(JSON_PATH):
            with open(JSON_PATH, "r", encoding='utf-8') as f:
                return json.loads(f.read())
    except Exception as e:
        print("配置文件读取错误:", e)
    return {"xuni_path": os.getcwd(), "pip_url": "https://mirrors.aliyun.com/pypi/simple/"}


def save_file(data):
    with open(JSON_PATH, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=4)


def is_path(value):
    """检查给定的值是否可能是路径（假设它是一个字符串并且看起来像路径）。"""
    return isinstance(value, str) and (os.path.sep in value or os.path.altsep and os.path.altsep in value)


def quote_paths_if_exist(command_list):
    for index, value in enumerate(command_list):
        if is_path(value) and " " in value:
            # 如果路径已经带有双引号，则不再添加
            if not (value.startswith('"') and value.endswith('"')):
                command_list[index] = '"{}"'.format(value)
    return command_list


def get_environ(cmd=''):
    cmd += f'set NUITKA_CACHE_DIR={NUITKACACHE_PATH}&&'
    cmd += f'set PYINSTALLER_CONFIG_DIR={PYINSTALLERCACHE_PATH}&&'
    cmd += f'set PIP_CACHE_DIR={PIPCACHE_PATH}&&'
    cmd += f'set PIP_INDEX_URL={open_file()["pip_url"]}&&'
    cmd += 'set NO_PROXY="*"&&'
    return cmd


def get_package_version(xuni_path, package):
    site_packages_path = os.path.join(xuni_path, "Lib", "site-packages")
    try:
        version_info = next(
            (value for value in os.listdir(site_packages_path) 
                if value.startswith(package + "-") and value.endswith(".dist-info")),
            None
        )
        if version_info:
            return version_info.replace(package + "-", "").replace(".dist-info", "")
        else:
            return ""
    except Exception as e:
        print(f"site-packages包 [ {package} ] 检查错误: {e}")
        return ""


def sub_cmd(commd, guide=None, call=print, reult=False):
    commds = ["cmd.exe", "/c", get_environ()] + quote_paths_if_exist(commd)
    if guide:
        commds.insert(0, guide)
    venv = subprocess.Popen(commds, 
        stdin=subprocess.PIPE, 
        stdout=subprocess.PIPE, 
        stderr=subprocess.STDOUT,
        shell=True,
        creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, # CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP
    )
    if not reult:
        for line in iter(venv.stdout.readline, b''):
            call(line.decode('gbk').strip())
    else:
        stdout, stderr = venv.communicate()
        if stderr is not None:
            call(stderr)
        return stdout


def remove_file(path, build_time):
    try:
        if os.stat(path).st_ctime > build_time:
            print("删除", path)
            if os.path.isdir(path):
                shutil.rmtree(path)
            else:
                os.remove(path)    
    except:
        pass



def nuitka_build_package(window, name_path, requi, py_main, py_args):
    build_time = time.time() 
    site_packages_path = os.path.join(name_path, "Lib", "site-packages")
    nuitka_path = os.path.join(site_packages_path, "nuitka")
    Scripts_Path = os.path.join(name_path, "Scripts")
    pip_path = os.path.join(Scripts_Path, "pip.exe")
    if not os.path.exists(nuitka_path): # 安装打包库 
        sub_cmd([pip_path, "install", "Nuitka"])
        version = get_package_version(name_path, "Nuitka")
        wx.CallAfter(window.version_textctrk.SetValue, version)
        time.sleep(0.5)
    if requi != "": # 安装依赖 
        sub_cmd([pip_path, "install", "-r", requi])
        time.sleep(0.5)
    print("开始构建", py_main)
    commd_args = [Scripts_Path + "\\python.exe", "-m", "nuitka", "--output-dir=dist", "--show-progress"]
    commd_args += py_args.split(" ")
    commd_args.append(py_main)
    sub_cmd(commd_args, guide="start")
    model_name = os.path.basename(py_main).replace(".py", "")
    build_model = window.choice_model.GetSelection()
    if build_model==1:
        for pack_name in window.addbinary_TextCtrl.GetValue().split(";"):
            if pack_name == "":
                continue
            try:
                pack_path = os.path.join(site_packages_path, pack_name)
                if os.path.exists(pack_path):
                    print("复制第三方库", pack_name)
                    shutil.copytree(pack_path, os.path.join(DIST_PATH, model_name + ".dist", pack_name))
            except:
                pass

    static_path = os.path.join(DIST_PATH, model_name + ".dist") if build_model==1 else DIST_PATH
    for pack_path in window.static_TextCtrl.GetValue().split(";"):
        if pack_path == "":
            continue
        try:
            if os.path.exists(pack_path):
                print("复制静态资源", pack_path)
                shutil.copytree(pack_path, os.path.join(static_path, os.path.basename(pack_path)))
        except:
            pass
    if window.checkbox_clear.GetValue():
        for path in [f"dist\\{model_name}.onefile-build", f"dist\\{model_name}.build", "nuitka-crash-report.xml"]:
            try:
                refie = os.path.join(MAIN_PATH, path)
                if os.path.exists(refie):
                    print("删除文件", refie)
                    if os.path.isdir(refie):
                        shutil.rmtree(refie)
                    else:
                        os.remove(refie)
            except:
                pass
        if build_model==0:
            shutil.rmtree(os.path.join(DIST_PATH, f"{model_name}.dist"))

    os.popen("explorer.exe {}".format(DIST_PATH))
    print("打包完成", py_main)
    wx.CallAfter(window.build_button.Enable, True)



def pyinstaller_build_package(window, name_path, requi, py_main, py_args):
    build_time = time.time() 
    Scripts_Path = os.path.join(name_path, "Scripts")
    pyinstaller_path = os.path.join(Scripts_Path, "pyinstaller.exe")
    pip_path = os.path.join(Scripts_Path, "pip.exe")
    if not os.path.exists(pyinstaller_path): # 安装打包库
        sub_cmd([pip_path, "install", "pyinstaller"])
        version = get_package_version(name_path, "pyinstaller")
        wx.CallAfter(window.version_textctrk.SetValue, version)
        time.sleep(0.5)
    if requi != "": # 安装依赖
        sub_cmd([pip_path, "install", "-r", requi])
        time.sleep(0.5)
    print("开始构建", py_main)
    if py_main.endswith(".py"):
        commd_args = py_args.split(" ")
        commd_args.insert(0, pyinstaller_path)
        commd_args.append(py_main)
        sub_cmd(commd_args)
    elif py_main.endswith(".spec"):
        sub_cmd([pyinstaller_path, py_main])

    if window.choice_model.GetSelection()==1:
        if py_main.endswith(".py"):
            model_name = os.path.basename(py_main).replace(".py", "")
            commd_args = py_args.split(" ")
            commd_args.pop(0)
            cargs = parse_arguments(commd_args)
            for val in ["n", "name"]:
                if val in cargs:
                    model_name = cargs[val]
        elif py_main.endswith(".spec"):
            with open(py_main, "r", encoding="utf-8") as f:
                model_name = re.search("name='(.*?)',", f.read(), re.S).group(1)
        static_path = os.path.join(DIST_PATH, model_name)
    else:
        static_path = DIST_PATH
    for pack_path in window.static_TextCtrl.GetValue().split(";"):
        if pack_path == "":
            continue
        try:
            if os.path.exists(pack_path):
                print("复制静态资源", pack_path)
                shutil.copytree(pack_path, os.path.join(static_path, os.path.basename(pack_path)))
        except:
            pass
    if window.checkbox_clear.GetValue():
        for val in os.listdir(MAIN_PATH):
            if val.endswith(".spec"):
                remove_file(os.path.join(MAIN_PATH, val), build_time)
        build_path = os.path.join(MAIN_PATH, "build")
        remove_file(build_path, build_time)
        if os.path.exists(build_path):
            for val in os.listdir(build_path):
                remove_file(os.path.join(build_path, val), build_time)

    os.popen("explorer.exe {}".format(DIST_PATH))
    print("打包完成", py_main)
    wx.CallAfter(window.build_button.Enable, True)



class FileDrop(wx.FileDropTarget):
    def __init__(self, windows, wxType, allowed_extensions=[]):
        wx.FileDropTarget.__init__(self)
        self.wxType = wxType
        self.windows = windows
        self.allowed_extensions = allowed_extensions

    def OnDropFiles(self, x, y, filePaths):
        if not filePaths:
            return False
        # 过滤文件路径，只保留允许的扩展名
        valid_files = [path for path in filePaths if any([path.endswith(name) for name in self.allowed_extensions])]
        if valid_files:
            if self.wxType == "TextCtrl":
                self.windows.SetValue(valid_files[0])
            elif self.wxType == "ComboBox":
                new_choices = self.windows.path_python.GetItems() + valid_files
                self.windows.path_python.Set(new_choices)
                self.windows.path_python.SetSelection(len(new_choices)-1)
                self.windows.on_select_python(None)
            return True
        else:
            return False



class DirDrop(wx.FileDropTarget):
    def __init__(self, windows, wxType):
        wx.FileDropTarget.__init__(self)
        self.wxType = wxType
        self.windows = windows

    def OnDropFiles(self, x, y, filePaths):
        if not filePaths:
            return False
        # 过滤文件路径，只保留目录
        valid_files = [path for path in filePaths if os.path.isdir(path)]
        if valid_files:
            if self.wxType == "TextCtrl":
                self.windows.SetValue(valid_files[0])
            elif self.wxType == "ComboBox":
                new_choices = self.windows.GetItems() + valid_files
                self.windows.Set(new_choices)
                self.windows.SetSelection(len(new_choices)-1)
            return True
        else:
            return False



class mythread(threading.Thread):
    def __init__(self, windows):
        threading.Thread.__init__(self)
        self.windows = windows
        self.daemon  = True
        self.loop    = asyncio.new_event_loop()

    def run(self):
        self.loop.run_forever()

    def coro(self, call, *agr):
        asyncio.run_coroutine_threadsafe(call(self.windows, *agr), self.loop)



class Pyd_Main(wx.Frame):
    def __init__(self, window, xnPath):
        super(Pyd_Main, self).__init__(window, size=wx.Size(600, 400))
        self.py_version = window.xu_dict[xnPath][1]
        self.xnPath = xnPath
        self.vs_path = r"C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vs_installer.exe"
        self.SetTitle("pyd构建 [{}] [{}]".format(self.xnPath, self.py_version))
        self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
        Msizer = wx.BoxSizer(wx.VERTICAL)

        sizer0 = wx.BoxSizer(wx.HORIZONTAL)
        self.path_main   = wx.TextCtrl(self)
        self.select_file = wx.Button(self, -1, "选择文件")
        self.select_dir  = wx.Button(self, -1, "选择目录")
        sizer0.Add(self.path_main, 1, wx.EXPAND, 5)
        sizer0.Add(self.select_file, 0, wx.EXPAND, 5)
        sizer0.Add(self.select_dir, 0, wx.EXPAND, 5)
        Msizer.Add(sizer0, 0, wx.EXPAND, 5)

        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
        self.cython_ctrl = wx.TextCtrl(self, value="Cython版本:")
        self.cython_ctrl.Enable(False)
        self.vsTools_button = wx.Button(self, -1, "")
        sizer1.Add(self.cython_ctrl, 1, wx.EXPAND, 5)
        sizer1.Add(self.vsTools_button, 1, wx.EXPAND, 5)
        Msizer.Add(sizer1, 0, wx.EXPAND, 5)

        sizer2 = wx.BoxSizer(wx.HORIZONTAL)
        self.build_button = wx.Button(self, -1, "生成")
        self.open_button = wx.Button(self, -1, "打开生成目录")
        sizer2.Add(self.build_button, 1, wx.EXPAND, 5)
        sizer2.Add(self.open_button, 1, wx.EXPAND, 5)
        Msizer.Add(sizer2, 0, wx.EXPAND, 5)

        self.richtext = wx.richtext.RichTextCtrl(self)
        self.richtext.SetEditable(False)
        Msizer.Add(self.richtext, 1, wx.EXPAND, 5)

        self.SetSizer(Msizer)
        self.Layout()
        self.Centre(wx.BOTH)
        self.drop = DirDrop(self.path_main, "TextCtrl")
        self.drop.OnDropFiles = self.OnDropFiles
        self.path_main.SetDropTarget(self.drop)
        self.build_button.Bind(wx.EVT_BUTTON, self.on_button)
        self.vsTools_button.Bind(wx.EVT_BUTTON, self.on_vs_tool)
        self.open_button.Bind(wx.EVT_BUTTON, lambda e : window.onOpenDir(BUILD_PATH))
        self.select_file.Bind(wx.EVT_BUTTON, self.On_OpenFile)
        self.select_dir.Bind(wx.EVT_BUTTON, self.On_OpenDir)
        self.build_button.Enable(False)
        self.vsTools_button.Enable(False)
        window.mythr.coro(self.on_init_cython)

    async def on_init_cython(self, window):
        try:
            cython_path = os.path.join(self.xnPath, "Scripts", "cython.exe")
            pip_path = os.path.join(self.xnPath, "Scripts", "pip.exe")
            if not os.path.exists(cython_path):
                sub_cmd([pip_path, "install", "Cython"], call=self.add_mesage)
            time.sleep(0.5)
            version = get_package_version(self.xnPath, "Cython")
            wx.CallAfter(self.cython_ctrl.SetValue, "Cython版本:" + version)
            label = "下载VS构建工具"
            if os.path.exists(self.vs_path):
                label = "打开VS构建工具"
            wx.CallAfter(self.vsTools_button.SetLabel, label)
        except Exception as e:
            wx.CallAfter(self.mesage_add, "初始化错误 {} ".format(e))
        wx.CallAfter(self.build_button.Enable, True)
        wx.CallAfter(self.vsTools_button.Enable, True)

    def on_vs_tool(self, eve):
        if self.vsTools_button.GetLabel() == "下载VS构建工具":
            webbrowser.open("https://visualstudio.microsoft.com/visual-cpp-build-tools/")
        elif self.vsTools_button.GetLabel() == "打开VS构建工具":
            if not os.path.exists(self.vs_path):
                self.vsTools_button.SetLabel("下载VS构建工具")
                return
            subprocess.Popen([self.vs_path])

    def add_mesage(self, msg):
        wx.CallAfter(self.mesage_add, msg)

    def mesage_add(self, data):
        current_datetime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        self.richtext.AppendText(current_datetime + " " + data + "\n")
        self.richtext.SetInsertionPointEnd()
        self.richtext.ShowPosition(self.richtext.GetLastPosition())

    def OnDropFiles(self, x, y, filePaths):
        if not filePaths:
            return False
        path_list = [ ]
        for path in filePaths:
            if os.path.isfile(path) and path.endswith(".py") or os.path.isdir(path):
                path_list.append(path)
        self.path_main.SetValue(";".join(path_list))
        return True

    def on_button(self, eve):
        self.build_button.Enable(False)
        def pyd_build(window, xnPath):
            try:
                py_app_list = [ ]
                for value in window.path_main.GetValue().split(";"):
                    if os.path.isdir(value):
                        for root, _, files in os.walk(value):
                            py_app_list.extend([os.path.join(root, file) for file in files if file.endswith(".py")])
                    elif os.path.isfile(value):
                        if value.endswith(".py"):
                            py_app_list.append(value)

                setup =  "# python 3.6 \n"
                setup += "from distutils.core import setup\nfrom Cython.Build import cythonize\n"
                setup += "a = {}\n".format(py_app_list)
                setup += "b = {}\n".format({'language_level': float(re.search(r"(\d+.\d+)", window.py_version).group(1))})
                setup += "e = cythonize(a, compiler_directives=b)\n"
                setup += "setup(ext_modules=e)\n"
                build_path  = os.path.join(BUILD_PATH, "build.py")
                python_path = os.path.join(xnPath, "Scripts", "python.exe")
                with open(build_path, "w", encoding="utf-8") as f:
                    f.write(setup)
                sub_cmd([python_path, build_path, "build_ext", "--build-lib=pyd_build"], call=window.add_mesage)
                for path in py_app_list:
                    name_file = os.path.splitext(path)[0] + ".c"
                    if os.path.exists(name_file):
                        wx.CallAfter(window.mesage_add, "移除 " + name_file)
                        os.remove(name_file)
                os.remove(build_path)
            except Exception as e:
                wx.CallAfter(window.mesage_add, "构建错误 {} ".format(e))
            wx.CallAfter(window.build_button.Enable, True)
        threading.Thread(target=pyd_build, args=(self, self.xnPath)).start()
        
    def On_OpenDir(self, eve):
        dlg = wx.DirDialog(self, message="选择py文件", style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            self.path_main.SetValue(dlg.GetPath())
        dlg.Destroy()

    def On_OpenFile(self, eve):
        dlg = wx.FileDialog(self, message="选择程序", wildcard="All files (*.py)|*.py", style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
        if dlg.ShowModal() == wx.ID_OK:
            self.path_main.SetValue(dlg.GetPath())
        dlg.Destroy()



class Nuitka_Main(wx.Frame):
    def __init__(self, parent, xnPath):
        wx.Frame.__init__(self, parent, title="Nuitka打包 [ {} ]".format(xnPath), size=wx.Size(600, 450))
        self.is_alive = True
        self.window=parent
        self.xnPath = xnPath
        self.plugin_enable_list = ["torch", "tensorflow", "keras", "paddle", "numpy", "pandas", "matplotlib"]
        self.enable_plugin_list = ["matplotlib", "multiprocessing", "glfw", "eventlet", "gevent", "kivy", "pyqt5", "pyqt6", "pyside2", "pyside6"]
        self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
        bSizer = wx.BoxSizer(wx.VERTICAL)

        bSizer0 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer0.Add(wx.StaticText(self, label="构建版本"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.version_textctrk = wx.TextCtrl(self)
        self.version_textctrk.Enable(False)
        self.version_button = wx.Button(self, -1, "安装此版本")
        self.version_choice = wx.Choice(self, choices=[])
        bSizer0.Add(self.version_textctrk, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        bSizer0.Add(self.version_choice, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        bSizer0.Add(self.version_button, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        bSizer.Add(bSizer0, 0, wx.EXPAND, 5)

        bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer1.Add(wx.StaticText(self, label="入口文件"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.py_pack_main_path = wx.TextCtrl(self)
        self.py_pack_main_path.SetDropTarget(FileDrop(self.py_pack_main_path, "TextCtrl", ['.py']))
        bSizer1.Add(self.py_pack_main_path, 1, wx.EXPAND, 5)
        bSizer.Add(bSizer1, 0, wx.EXPAND, 5)

        bSizer2 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer2.Add(wx.StaticText(self, label="依赖文件"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.py_pack_yl_path = wx.TextCtrl(self)
        self.py_pack_yl_path.SetDropTarget(FileDrop(self.py_pack_yl_path, "TextCtrl", ['.txt']))
        bSizer2.Add(self.py_pack_yl_path, 1, wx.EXPAND, 5)
        bSizer.Add(bSizer2, 0, wx.EXPAND, 5)

        bSizer3 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer3.Add(wx.StaticText(self, label="打包参数"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.py_pack_args = wx.TextCtrl(self, value="--standalone --mingw64")
        bSizer3.Add(self.py_pack_args, 1, wx.EXPAND, 5)
        bSizer.Add(bSizer3, 0, wx.EXPAND, 5)

        self.checkbox_clear = wx.CheckBox(self, label="清理打包残留")
        bSizer.Add(self.checkbox_clear, 0, wx.EXPAND, 5)
        self.checkbox_clear.SetValue(True)

        self.build_button = wx.Button( self, -1, "打包")
        bSizer.Add(self.build_button, 0, wx.EXPAND, 5)
        bSizer.Add( (0, 0), 1, wx.EXPAND, 5 )

        bSizer_static = wx.BoxSizer(wx.HORIZONTAL)
        self.static_TextCtrl = wx.TextCtrl(self)
        self.static_button = wx.Button(self, -1, "添加静态资源")
        bSizer_static.Add( self.static_TextCtrl, 1, wx.EXPAND, 5 )
        bSizer_static.Add( self.static_button, 0, wx.EXPAND, 5 )
        bSizer.Add( bSizer_static, 0, wx.EXPAND, 5 )

        bSizer_addbinary = wx.BoxSizer(wx.HORIZONTAL)
        self.addbinary_TextCtrl = wx.TextCtrl(self)
        self.copy_button = wx.Button(self, -1, "添加第三方库")
        bSizer_addbinary.Add( self.addbinary_TextCtrl, 1, wx.EXPAND, 5 )
        bSizer_addbinary.Add( self.copy_button, 0, wx.EXPAND, 5 )
        bSizer.Add( bSizer_addbinary, 0, wx.EXPAND, 5 )

        bSizer_CheckBox = wx.BoxSizer(wx.HORIZONTAL)
        self.choice_model = wx.Choice(self, choices=["单文件", "多文件"])
        self.choice_model.SetSelection(1)
        self.checkbox_mingw = wx.CheckBox(self, label="mingw64编译")
        self.checkbox_mingw.SetValue(True)
        self.checkbox_windw = wx.CheckBox(self, label="去掉黑窗口")
        bSizer_CheckBox.Add( self.choice_model, 1, wx.EXPAND, 5 )
        bSizer_CheckBox.Add( self.checkbox_mingw, 0, wx.EXPAND, 5 )
        bSizer_CheckBox.Add( self.checkbox_windw, 0, wx.EXPAND, 5 )
        bSizer.Add( bSizer_CheckBox, 0, wx.EXPAND, 5 )

        for value in self.plugin_enable_list:
            setattr(self, "plugin_enable_" + value, wx.CheckBox(self, label=value))
        for value in self.enable_plugin_list:
            setattr(self, "enable_plugin_" + value, wx.CheckBox(self, label=value))

        sizer_module = wx.StaticBoxSizer(wx.StaticBox(self, -1, '模块'), wx.VERTICAL)
        self.on_init_model(sizer_module, self.plugin_enable_list[:], "plugin_enable_", 2)
        bSizer.Add( sizer_module, 0, wx.EXPAND, 5 )

        sizer_plugin = wx.StaticBoxSizer(wx.StaticBox(self, -1, '插件'), wx.VERTICAL)
        self.on_init_model(sizer_plugin, self.enable_plugin_list[:], "enable_plugin_", 3)
        bSizer.Add( sizer_plugin, 0, wx.EXPAND, 5)
        
        self.SetSizer(bSizer)
        self.Layout()
        self.Centre(wx.BOTH)
        self.version_button.Bind(wx.EVT_BUTTON, lambda eve : self.window.on_build_install(self, "Nuitka", self.xnPath))
        self.build_button.Bind(wx.EVT_BUTTON,  self.on_button)
        self.copy_button.Bind(wx.EVT_BUTTON,   self.on_OpenDir)
        self.static_button.Bind(wx.EVT_BUTTON, self.on_OpenFile)
        self.choice_model.Bind(wx.EVT_CHOICE,     self.on_CheckBox)
        self.checkbox_mingw.Bind(wx.EVT_CHECKBOX, self.on_CheckBox)
        self.checkbox_windw.Bind(wx.EVT_CHECKBOX, self.on_CheckBox)
        for value in self.plugin_enable_list:
            getattr(self, "plugin_enable_" + value).Bind(wx.EVT_CHECKBOX, self.on_CheckBox)
        for value in self.enable_plugin_list:
            getattr(self, "enable_plugin_" + value).Bind(wx.EVT_CHECKBOX, self.on_CheckBox)
        self.window.mythr.coro(build_version_init, self, "Nuitka")
        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def OnClose(self, event):
        self.is_alive = False
        self.Destroy() 

    def on_init_model(self, sizer_model, copy_list, enpl, index):
        for sizer in [wx.BoxSizer(wx.HORIZONTAL) for i in range(index)]:
            for i in range(4):
                if len(copy_list)>0:
                    value = copy_list[0]
                    sizer.Add(getattr(self, enpl + value), 1, wx.EXPAND, 5)
                    copy_list.remove(value)
                else:
                    sizer.Add((0, 0), 1, wx.EXPAND, 5)
            sizer_model.Add(sizer, 0, wx.EXPAND, 5)

    def on_CheckBox(self, eve):
        commd = "--onefile " if self.choice_model.GetSelection() == 0 else ""
        commd += "--standalone"
        if self.checkbox_mingw.GetValue():
            commd += " --mingw64"
        if self.checkbox_windw.GetValue():
            commd += " --windows-disable-console"
        for value in self.plugin_enable_list:
            if getattr(self, "plugin_enable_" + value).GetValue():
                commd += " --plugin-enable=" + value
        for value in self.enable_plugin_list:
            if getattr(self, "enable_plugin_" + value).GetValue():
                commd += " --enable-plugin=" + value
        self.py_pack_args.SetValue(commd)

    def on_OpenDir(self, eve):
        dlg = wx.DirDialog(
            self, 
            message="选择包",
            defaultPath=os.path.join(self.xnPath, "Lib", "site-packages"),  # 可以设置默认目录
            style=wx.DD_DEFAULT_STYLE  # 设置打开文件，并且文件必须存在
        )
        if dlg.ShowModal() == wx.ID_OK:
            value = self.addbinary_TextCtrl.GetValue() + ";" + os.path.basename(dlg.GetPath())
            self.addbinary_TextCtrl.SetValue(value)
        dlg.Destroy()

    def on_OpenFile(self, eve):
        py_path = self.py_pack_main_path.GetValue()
        if py_path.endswith(".py") and os.path.exists(py_path):
            py_path = os.path.dirname(py_path)
        else:
            py_path = "C:\\Users\\" + os.environ["USERNAME"] + "\\Desktop"
        dlg = wx.DirDialog(
            self, 
            message="选择资源目录",
            defaultPath=py_path,  # 可以设置默认目录
            style=wx.DD_DEFAULT_STYLE  # 设置打开文件，并且文件必须存在
        )
        if dlg.ShowModal() == wx.ID_OK:
            value = self.static_TextCtrl.GetValue() + ";" + dlg.GetPath()
            self.static_TextCtrl.SetValue(value)
        dlg.Destroy()

    def on_button(self, eve):
        self.build_button.Enable(False)
        threading.Thread(target=nuitka_build_package, args=(
            self, 
            self.xnPath, 
            self.py_pack_yl_path.GetValue(),
            self.py_pack_main_path.GetValue(),
            self.py_pack_args.GetValue()
            )).start()



class Pyinstaller_Main(wx.Frame):
    def __init__(self, parent, xnPath):
        wx.Frame.__init__(self, parent, title="Pyinstaller打包 [ {} ]".format(xnPath), size=wx.Size(600, 450))
        self.is_alive = True
        self.window=parent
        self.xnPath = xnPath
        self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
        bSizer = wx.BoxSizer(wx.VERTICAL)

        bSizer0 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer0.Add(wx.StaticText(self, label="构建版本"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.version_textctrk = wx.TextCtrl(self)
        self.version_textctrk.Enable(False)
        self.version_button = wx.Button(self, -1, "安装此版本")
        self.version_choice = wx.Choice(self, choices=[])
        bSizer0.Add(self.version_textctrk, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        bSizer0.Add(self.version_choice, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        bSizer0.Add(self.version_button, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        bSizer.Add(bSizer0, 0, wx.EXPAND, 5)

        bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer1.Add(wx.StaticText(self, label="入口文件"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.py_pack_main_path = wx.TextCtrl(self)
        self.py_pack_main_path.SetDropTarget(FileDrop(self.py_pack_main_path, "TextCtrl", ['.py', '.spec']))
        bSizer1.Add(self.py_pack_main_path, 1, wx.EXPAND, 5)
        bSizer.Add(bSizer1, 0, wx.EXPAND, 5)

        bSizer2 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer2.Add(wx.StaticText(self, label="依赖文件"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.py_pack_yl_path = wx.TextCtrl(self)
        self.py_pack_yl_path.SetDropTarget(FileDrop(self.py_pack_yl_path, "TextCtrl", ['.txt']))
        bSizer2.Add(self.py_pack_yl_path, 1, wx.EXPAND, 5)
        bSizer.Add(bSizer2, 0, wx.EXPAND, 5)

        bSizer3 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer3.Add(wx.StaticText(self, label="打包参数"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.py_pack_args = wx.TextCtrl(self, value="-F")
        bSizer3.Add(self.py_pack_args, 1, wx.EXPAND, 5)
        bSizer.Add(bSizer3, 0, wx.EXPAND, 5)

        self.checkbox_clear = wx.CheckBox(self, label="清理打包残留")
        bSizer.Add(self.checkbox_clear, 0, wx.EXPAND, 5)
        self.checkbox_clear.SetValue(True)

        self.build_button = wx.Button( self, -1, "打包")
        bSizer.Add(self.build_button, 0, wx.EXPAND, 5)
        bSizer.Add( (0, 0), 1, wx.EXPAND, 5 )

        bSizer_static = wx.BoxSizer(wx.HORIZONTAL)
        self.static_TextCtrl = wx.TextCtrl(self)
        self.static_button = wx.Button(self, -1, "添加静态资源")
        bSizer_static.Add( self.static_TextCtrl, 1, wx.EXPAND, 5 )
        bSizer_static.Add( self.static_button, 0, wx.EXPAND, 5 )
        bSizer.Add( bSizer_static, 0, wx.EXPAND, 5 )

        self.bSizer_addbinary = wx.BoxSizer(wx.HORIZONTAL)
        self.addbinary_TextCtrl = wx.TextCtrl(self)
        filedrop_ps = FileDrop(self.addbinary_TextCtrl, "TextCtrl", [''])
        filedrop_ps.OnDropFiles = self.OnDropFiles
        self.addbinary_TextCtrl.SetDropTarget(filedrop_ps)
        self.bSizer_addbinary.Add( self.addbinary_TextCtrl, 1, wx.EXPAND, 5 )
        bSizer.Add( self.bSizer_addbinary, 0, wx.EXPAND, 5 )
        self.addbinary_TextCtrl.Show(False)

        bSizer_CheckBox = wx.BoxSizer(wx.HORIZONTAL)
        self.choice_model = wx.Choice(self, choices=["单文件", "多文件"])
        self.choice_model.SetSelection(0)
        bSizer_CheckBox.Add( self.choice_model, 1, wx.EXPAND, 5 )
        self.winCheckBox = wx.CheckBox(self, label="去掉黑窗口")
        bSizer_CheckBox.Add( self.winCheckBox, 0, wx.EXPAND, 5 )
        self.debugCheckBox = wx.CheckBox(self, label="静态资源")
        bSizer_CheckBox.Add( self.debugCheckBox, 0, wx.EXPAND, 5 )
        bSizer.Add( bSizer_CheckBox, 0, wx.EXPAND, 5 )

        self.SetSizer(bSizer)
        self.Layout()
        self.Centre(wx.BOTH)
        self.version_button.Bind(wx.EVT_BUTTON, lambda eve : self.window.on_build_install(self, "pyinstaller", self.xnPath))
        self.build_button.Bind(wx.EVT_BUTTON, self.on_button)
        self.choice_model.Bind(wx.EVT_CHOICE, self.on_edit)
        self.winCheckBox.Bind(wx.EVT_CHECKBOX, self.on_edit)
        self.static_button.Bind(wx.EVT_BUTTON, self.on_OpenFile)
        self.debugCheckBox.Bind(wx.EVT_CHECKBOX, self.on_addbinary)
        self.window.mythr.coro(build_version_init, self, "pyinstaller")
        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def OnClose(self, event):
        self.is_alive = False
        self.Destroy() 

    def OnDropFiles(self, x, y, filePaths):
        if not filePaths:
            return False
        if filePaths:
            value = self.addbinary_TextCtrl.GetValue() 
            value += "{};".format(filePaths[0])
            self.addbinary_TextCtrl.SetValue(value)
            self.on_edit(None)
            return True
        else:
            return False

    def on_OpenFile(self, eve):
        py_path = self.py_pack_main_path.GetValue()
        if py_path.endswith(".py") and os.path.exists(py_path) or py_path.endswith(".spec") and os.path.exists(py_path):
            py_path = os.path.dirname(py_path)
        else:
            py_path = "C:\\Users\\" + os.environ["USERNAME"] + "\\Desktop"
        dlg = wx.DirDialog(
            self, 
            message="选择资源目录",
            defaultPath=py_path,  # 可以设置默认目录
            style=wx.DD_DEFAULT_STYLE  # 设置打开文件，并且文件必须存在
        )
        if dlg.ShowModal() == wx.ID_OK:
            value = self.static_TextCtrl.GetValue() + ";" + dlg.GetPath()
            self.static_TextCtrl.SetValue(value)
        dlg.Destroy()

    def on_edit(self, eve):
        commd = "-F" if self.choice_model.GetSelection() == 0 else "-D"
        if self.winCheckBox.GetValue():
            commd += "w"
        if self.debugCheckBox.GetValue():
            value = self.addbinary_TextCtrl.GetValue()
            for val in value.split(";"):
                if val != "":
                    commd += " --add-binary {};./".format(val)
        self.py_pack_args.SetValue(commd)

    def on_addbinary(self, eve):
        if self.debugCheckBox.GetValue():
            self.addbinary_TextCtrl.Show(True)
        else:
            self.addbinary_TextCtrl.Show(False)
        self.on_edit(eve)
        self.Layout()

    def on_button(self, eve):
        self.build_button.Enable(False)
        threading.Thread(target=pyinstaller_build_package, args=(
            self, 
            self.xnPath, 
            self.py_pack_yl_path.GetValue(),
            self.py_pack_main_path.GetValue(),
            self.py_pack_args.GetValue()
            )).start()



class Main(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, title="虚拟环境管理", size=wx.Size(800, 400))
        self.config = open_file()
        self.xu_dict = { }
        self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
        bSizer = wx.BoxSizer(wx.VERTICAL)

        bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer1.Add(wx.StaticText(self, label="虚拟路径"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.path_xuni = wx.TextCtrl(self, value=self.config["xuni_path"], style=wx.TE_PROCESS_ENTER)
        self.path_xuni.SetDropTarget(DirDrop(self.path_xuni, "TextCtrl"))
        bSizer1.Add(self.path_xuni, 1, wx.EXPAND, 5)
        self.open_dir_button = wx.Button(self, label="打开目录")
        self.save_button = wx.Button(self, label="保存配置")
        bSizer1.Add(self.open_dir_button, 0, wx.EXPAND, 5)
        bSizer1.Add(wx.StaticText(self, label="PIP镜像"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.pip_url = wx.TextCtrl(self, value=self.config["pip_url"])
        bSizer1.Add(self.pip_url, 1, wx.EXPAND, 5)
        bSizer1.Add(self.save_button, 0, wx.EXPAND, 5)
        bSizer.Add(bSizer1, 0, wx.EXPAND, 5)

        bSizer2 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer2.Add(wx.StaticText(self, label="真实路径"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        self.py_version = wx.TextCtrl(self, value="")
        self.py_version.Enable(False)
        pyreglist = get_py_reg()
        self.path_python = wx.ComboBox(self, value=pyreglist[0], choices=pyreglist)
        self.path_python.SetDropTarget(FileDrop(self, "ComboBox", ['python.exe', 'python3.exe']))
        self.xuni_name = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER)
        self.pip_check = wx.CheckBox(self, label="升级pip")
        self.pip_check.SetValue(True)
        self.creat_button = wx.Button(self, label="创建虚拟环境")
        bSizer2.Add(self.path_python, 1, wx.EXPAND, 5)
        bSizer2.Add(self.py_version, 0, wx.EXPAND, 5)
        bSizer2.Add(self.pip_check, 0, wx.EXPAND, 5)
        bSizer2.Add(self.xuni_name, 0, wx.EXPAND, 5)
        bSizer2.Add(self.creat_button, 0, wx.EXPAND, 5)
        bSizer.Add(bSizer2, 0, wx.EXPAND, 5)

        bSizer3 = wx.BoxSizer(wx.HORIZONTAL)
        pflist = [v.device for v in psutil.disk_partitions()]
        self.saomiao_text = wx.ComboBox(self, value=pflist[0], choices=pflist)
        self.saomiao_text.SetDropTarget(DirDrop(self.saomiao_text, "ComboBox"))
        self.pyd_button = wx.Button(self, label="生成pyd")
        self.saomiao_button = wx.Button(self, label="扫描虚拟环境")
        bSizer3.Add(self.pyd_button, 0, wx.EXPAND, 5)
        bSizer3.Add(self.saomiao_button, 0, wx.EXPAND, 5)
        bSizer3.Add(self.saomiao_text, 1, wx.EXPAND, 5)
        bSizer.Add(bSizer3, 0, wx.EXPAND, 5)

        self.sizer_model = wx.BoxSizer( wx.VERTICAL )
        self.scroller = wx.ScrolledWindow(self)
        self.scroller.SetScrollRate(0, 30)
        self.scroller.SetSizer(self.sizer_model)
        bSizer.Add(self.scroller, 1, wx.EXPAND, 5)

        self.SetSizer(bSizer)
        self.Layout()
        self.Centre(wx.BOTH)

        self.creat_button.Bind(wx.EVT_BUTTON, self.onCreat_button)
        self.saomiao_button.Bind(wx.EVT_BUTTON, self.onSaoMiao)
        self.xuni_name.Bind(wx.EVT_TEXT_ENTER, self.onCreat_button)
        self.path_xuni.Bind(wx.EVT_TEXT_ENTER, lambda e : self.onOpenDir(self.path_xuni))
        self.open_dir_button.Bind(wx.EVT_BUTTON, lambda e : self.onOpenDir(self.path_xuni))
        self.path_python.Bind(wx.EVT_COMBOBOX, self.on_select_python)
        self.pyd_button.Bind(wx.EVT_BUTTON, self.onPytoPyd)
        self.save_button.Bind(wx.EVT_BUTTON, self.on_save)
        self.Bind(wx.EVT_CLOSE, self.on_save)
        self.mythr = mythread(self)
        self.mythr.start()
        self.mythr.coro(get_all_xuni, self.path_xuni.GetValue())
        self.on_select_python(None)

    def on_build_install(self, model, package, xnPath):
        model.version_button.Enable(False)
        ver = model.version_choice.GetString(model.version_choice.GetSelection())
        async def model_install(self, model, package, ver, xnPath):
            try:
                pip_path = os.path.join(xnPath, "Scripts", "pip")
                site_path = os.path.join(xnPath, "Lib", "site-packages")
                sub_cmd([pip_path, "install", package + "==" + ver])
                version = get_package_version(xnPath, package)
                wx.CallAfter(model.version_textctrk.SetValue, version)
            except Exception as e:
                print(f"site-packages包 [ {package} ] 安装失败", e)
            wx.CallAfter(model.version_button.Enable, True)
        self.mythr.coro(model_install, model, package, ver, xnPath)

    def onOpenDir(self, obj):
        if type(obj) == str:
            open_path = obj
        elif hasattr(obj, "GetValue"):
            open_path = obj.GetValue()
        os.system("explorer.exe {}".format(open_path))

    def on_button(self, openui, name_path):
        openui(self, name_path).Show()

    def on_select_python(self, eve):
        file_path = os.path.join(os.path.dirname(self.path_python.GetValue()), "NEWS.txt")
        with open(file_path, "r", encoding="utf-8") as file:
            for current_line_number, line in enumerate(file, start=1):
                if current_line_number == 5:
                    py_version = re.search(r'\b\d+(\.\d+)+\b', line.strip())
                    if py_version:
                        self.py_version.SetValue(py_version.group())
                        self.Layout()
                        break

    def on_save(self, eve):
        cdict = {"xuni_path": self.path_xuni.GetValue(), "pip_url":self.pip_url.GetValue()}
        save_file(cdict)
        if not os.path.exists(cdict["xuni_path"]):
            print("创建目录:", cdict["xuni_path"])
            os.makedirs(cdict["xuni_path"], exist_ok=True)
        print("保存完成", cdict)
        eve.Skip()

    def onPytoPyd(self, eve):
        class CallDialog(wx.Dialog):
            def __init__(self, window):
                super(CallDialog, self).__init__(window, title="环境选择")
                self.window = window
                self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
                self.SetSize(wx.Size(200, 120))
                sizer = wx.BoxSizer(wx.VERTICAL)
                self.choice_xn_path = wx.Choice(self, choices=[val for val in self.window.xu_dict])
                self.choice_xn_path.SetSelection(0)
                self.ok_button = wx.Button(self, label="确定")
                sizer.Add(self.choice_xn_path, 0, wx.EXPAND|wx.ALL, 5)
                sizer.Add(self.ok_button, 0, wx.EXPAND|wx.ALL, 5)
                self.SetSizer(sizer)
                self.Layout()
                self.Centre(wx.BOTH)
                self.ok_button.Bind(wx.EVT_BUTTON, self.on_button)
                self.Bind(wx.EVT_CLOSE, self.OnClose)

            def OnClose(self, eve):
                self.Destroy()

            def on_button(self, eve):
                Pyd_Main(self.window, self.choice_xn_path.GetString(self.choice_xn_path.GetSelection())).Show()
                self.Close()
        dialog = CallDialog(self)
        dialog.ShowModal()

    def onSaoMiao(self, eve):
        self.saomiao_button.Enable(False)
        self.mythr.coro(find_pyvenv_cfg, self.saomiao_text.GetValue())

    def onCreat_button(self, evt):
        self.creat_button.Enable(False)
        self.mythr.coro(creat_venv, self.path_python.GetValue(), self.path_xuni.GetValue() + "\\" + self.xuni_name.GetValue())

    def onDelete_button(self, layout, name_path, button):
        answ = wx.MessageBox("确定删除[{}]吗".format(name_path), "提示", wx.YES_NO, self)
        if answ == wx.YES:
            button.Enable(False)
            self.mythr.coro(delete_venv, layout, name_path)

    def onCd_button(self, name_path):
        class CallDialog(wx.Dialog):
            def __init__(self, window):
                super(CallDialog, self).__init__(window, title="工作目录")
                self.window = window
                self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
                self.SetSize(wx.Size(360, 120))
                sizer = wx.BoxSizer(wx.VERTICAL)
                self.run_cmd_dir = wx.TextCtrl(self, value=os.getcwd())
                self.run_cmd_dir.SetDropTarget(DirDrop(self.run_cmd_dir, "TextCtrl"))
                self.cmd_button = wx.Button(self, label="CMD")
                self.ipy_button = wx.Button(self, label="Ipython")
                hsizer = wx.BoxSizer(wx.HORIZONTAL)
                hsizer.Add(self.cmd_button, 1, wx.EXPAND|wx.ALL, 5)
                hsizer.Add(self.ipy_button, 1, wx.EXPAND|wx.ALL, 5)
                sizer.Add(self.run_cmd_dir, 0, wx.EXPAND|wx.ALL, 5)
                sizer.Add(hsizer, 0, wx.EXPAND|wx.ALL, 5)
                self.SetSizer(sizer)
                self.Layout()
                self.Centre(wx.BOTH)
                self.cmd_button.Bind(wx.EVT_BUTTON, self.on_cmd_call)
                self.ipy_button.Bind(wx.EVT_BUTTON, self.on_ipy_call)
                self.Bind(wx.EVT_CLOSE, self.OnClose)

            def OnClose(self, eve):
                self.Destroy()

            def on_cmd_call(self, eve):
                self.window.mythr.coro(cd_venv, self.run_cmd_dir.GetValue(), name_path[0], name_path[1])
                self.Close()

            def on_ipy_call(self, eve):
                self.window.mythr.coro(cd_ipython, self.run_cmd_dir.GetValue(), name_path[0], name_path[1])
                self.Close()

        dialog = CallDialog(self)
        dialog.ShowModal()

    def on_clear_package(self, name_path, button):
        button.Enable(False)
        self.mythr.coro(clear_package, button, name_path)

    def add_model(self, args):
        if args[0] in self.xu_dict:
            return
        sizer_h = wx.BoxSizer( wx.HORIZONTAL )
        wxText_name = wx.TextCtrl( self.scroller, -1, args[0], style=wx.TE_READONLY)
        wxText_version = wx.TextCtrl( self.scroller, -1, args[1], style=wx.TE_READONLY)
        button_cd = wx.Button( self.scroller, -1, "进入")
        button_clear = wx.Button( self.scroller, -1, "清理所有库")
        pyin_button = wx.Button( self.scroller, -1, "Pyinstaller打包")
        nuit_button = wx.Button( self.scroller, -1, "Nuitka打包")
        button_sc = wx.Button( self.scroller, -1, "删除")
        sizer_h.Add( wxText_name, 1, wx.ALL, 5 )
        sizer_h.Add( wxText_version, 0, wx.ALL, 5 )
        sizer_h.Add( button_cd, 0, wx.ALL, 5 )
        sizer_h.Add( button_clear, 0, wx.ALL, 5 )
        sizer_h.Add( pyin_button, 0, wx.ALL, 5 )
        sizer_h.Add( nuit_button, 0, wx.ALL, 5 )
        sizer_h.Add( button_sc, 0, wx.ALL, 5 )
        self.sizer_model.Add(sizer_h, 0, wx.EXPAND|wx.ALL, 5 )
        button_cd.Bind(wx.EVT_BUTTON, lambda evt : self.onCd_button(args))
        button_sc.Bind(wx.EVT_BUTTON, lambda evt : self.onDelete_button(sizer_h, args[0], button_sc))
        button_clear.Bind(wx.EVT_BUTTON, lambda evt : self.on_clear_package(args[0], button_clear))
        nuit_button.Bind(wx.EVT_BUTTON, lambda eve : self.on_button(Nuitka_Main, args[0]))
        pyin_button.Bind(wx.EVT_BUTTON, lambda eve : self.on_button(Pyinstaller_Main, args[0]))
        self.xu_dict[args[0]] = (sizer_h, args[1])
        self.Layout()

    def del_model(self, name_path, layout):
        self.sizer_model.Detach(layout)
        layout.Clear(True)
        layout.Destroy()
        if name_path in self.xu_dict:
            self.xu_dict.pop(name_path)
        self.Layout()



async def build_version_init(self, obj, model):
    def safe_call(func):
        def wrapper():
            if getattr(obj, 'is_alive', True):
                func()
        return wrapper
    wx.CallAfter(safe_call(lambda: obj.build_button.Enable(False)))
    try:
        site_path = os.path.join(obj.xnPath, "Lib", "site-packages")
        version = get_package_version(obj.xnPath, model)
        wx.CallAfter(safe_call(lambda: obj.version_textctrk.SetValue(version)))
        pip_path = os.path.join(obj.xnPath, "Scripts", "pip")
        reult = sub_cmd([pip_path, "install", model + "==0"], reult=True)
        reult = re.search(r"\(from versions: (.*?)\)", reult.decode("gbk"), re.S)
        if reult:
            ver_list = [value.strip() for value in reult.group(1).split(",")]
            wx.CallAfter(safe_call(lambda: obj.version_choice.Set(ver_list)))
            def set_selection():
                if version and version in ver_list:
                    obj.version_choice.SetSelection(ver_list.index(version))
                else:
                    obj.version_choice.SetSelection(0)
            wx.CallAfter(safe_call(set_selection))
    except Exception as e:
        print(f"包 [ {model} ] 版本查询失败", e)
    wx.CallAfter(safe_call(lambda: obj.build_button.Enable(True)))


async def get_all_xuni(self, xnPath):
    for name in os.listdir(xnPath):
        cfgPath = os.path.join(xnPath, name, "pyvenv.cfg")
        if os.path.exists(cfgPath):
            py_ver = get_py_xuni_version(cfgPath)
            wx.CallAfter(self.add_model, (os.path.join(xnPath, name), py_ver))


async def find_pyvenv_cfg(self, start_dir):
    found_files = [ ]
    for root, dirs, files in os.walk(start_dir):
        print(root)
        if 'pyvenv.cfg' in files:
            if os.path.exists(os.path.join(root, "Lib")) and os.path.exists(os.path.join(root, "Scripts")):
                pyvenv_path = os.path.join(root, 'pyvenv.cfg')
                py_ver = get_py_xuni_version(pyvenv_path)
                wx.CallAfter(self.add_model, (root, py_ver))
                found_files.append(pyvenv_path)
    print("扫描结果", found_files)
    wx.CallAfter(self.saomiao_button.Enable, True)


async def creat_venv(self, py, name):
    sub_cmd([py, "-m", "venv", name])
    time.sleep(0.5)
    pyvenv_path = os.path.join(name, "pyvenv.cfg")
    if os.path.exists(pyvenv_path):
        py_ver = get_py_xuni_version(pyvenv_path)
        if self.pip_check.GetValue():
            python_path = os.path.join(name, "Scripts", "python")
            sub_cmd([python_path, "-m", "pip", "install", "--upgrade", "pip", "setuptools"])
        wx.CallAfter(self.add_model, (name, py_ver))
    wx.CallAfter(self.creat_button.Enable, True)


async def cd_venv(self, cwd, name, version):
    commands_str = f"title [{name}] [python版本:{version}] [工作目录:{cwd}]&&" + get_environ() + name + '\\Scripts\\activate.bat'
    venv = subprocess.Popen(f'cmd.exe /k "{commands_str}"', cwd=cwd, creationflags=subprocess.CREATE_NEW_CONSOLE)

async def cd_ipython(self, cwd, name, version):
    if not get_package_version(name, "ipython"):
        print("正在安装Ipython")
        pip_path = os.path.join(name, "Scripts", "pip")
        sub_cmd([pip_path, "install", "ipython"])
    ipy_title = f"[{name}] [python版本:{version}] [工作目录:{cwd}]"
    ipython_cmd = os.path.join(name, "Scripts", "ipython")
    commands_str = f'{get_environ()}{ipython_cmd} --TerminalInteractiveShell.term_title=True --TerminalInteractiveShell.term_title_format="{ipy_title}"'
    venv = subprocess.Popen(f'cmd.exe /k "{commands_str}"', cwd=cwd, creationflags=subprocess.CREATE_NEW_CONSOLE)

async def delete_venv(self, layout, name_path):
    sub_cmd(["rmdir", "/s", "/q", name_path])
    wx.CallAfter(self.del_model, name_path, layout)


async def clear_package(self, button, name_path):
    print("开始清理", name_path)
    bmd_list = ["certifi", "pip", "setuptools", "pkg_resources"] # 删除白名单
    Scripts_Path = os.path.join(name_path, "Scripts")
    site_packages_path = os.path.join(name_path, "Lib", "site-packages")
    
    start_time = os.stat(os.path.join(Scripts_Path)).st_ctime + 30
    for lib in os.listdir(site_packages_path):
        lib_path = os.path.join(site_packages_path, lib)
        if any([any([lib_path.endswith(val) for val in bmd_list]), any([lib.startswith(val + "-") for val in bmd_list])]):
            continue
        try:
            lib_time = os.stat(lib_path).st_ctime
            if lib_time > start_time:
                if os.path.isdir(lib_path):
                    shutil.rmtree(lib_path)
                else:
                    os.remove(lib_path)
                print("删除", lib_path, lib_time)
        except:
            pass

    for scr in os.listdir(Scripts_Path):
        scr_path = os.path.join(Scripts_Path, scr)
        if any([scr.startswith(val) for val in ["pip", "activate", "Activate", "deactivate", "python.exe", "pythonw.exe"]]):
            continue
        try:
            scr_time = os.stat(scr_path).st_ctime
            if scr_time > start_time:
                if os.path.isdir(scr_path):
                    shutil.rmtree(scr_path)
                else:
                    os.remove(scr_path) 
                print("删除", scr_path, scr_time)
        except:
            pass
    print("清理完成", name_path)
    wx.CallAfter(button.Enable, True)



if __name__ == '__main__':
    app = wx.App()
    Main(None).Show(True)
    app.MainLoop()